<?php 
/**
 * This file is part of Dom framework.
 * 
 * Dom framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Dom framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Dom framework.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @link http://inditel.ee/
 * @copyright 2007-2009 Inditel Meedia OÜ
 * @author Oliver Leisalu <oliver@inditel.ee>
 */


/**
* Debug function. Useful for debugging your script. Outputs given argument.
* @param Mixed $data
* @package Dom
*/
function debug( $data ) {
	try {
		throw new Exception();
	} catch( Exception $e ) {
		$trace  = $e->getTrace();
		$desc = array_shift($trace);
		$info = $desc['file'].' on line '.$desc['line'].'';
		$infoS = basename($desc['file']).'('.$desc['line'].')';
	}
	$errTempId = 'e'.mt_rand(0,9999999999999);
	$onClick = "e=document.getElementById('".$errTempId."');if(e.style.display=='none'){e.style.display='';}else{e.style.display='none';}";
	echo '<strong style="color:#006699"><acronym style="cursor:pointer;" title="'.$info.'" onClick="'.$onClick.'"><span style="font-size:11px; font-weight:normal;">'.$infoS.' debug:</span></acronym></strong><span id="'.$errTempId.'">';
	if( is_object($data) === true or is_array($data) === true ) {
		echo '<pre>';
		print_r($data);
		echo '</pre><br />';
	} else if( is_null($data) ) {
		echo '<i>NULL</i><br />';
	} else {
		echo $data.'<br />';
	}
	echo '</span>';
	
	
}

if( function_exists('ip') == false ) {
/**
* @package DomBase
* @author Oliver Leisalu
*/
function ip( $a=1 ) { // algvrtus 1 
    $ip=getenv(HTTP_X_FORWARDED_FOR) ? getenv(HTTP_X_FORWARDED_FOR) : getenv(REMOTE_ADDR); // leiame IP 
    if (strstr($ip, ",")) { $ipm = explode(",", $ip); $ip = trim($ipm[0]); } // kui sisaldab koma, vtame selle, mis on enne koma 
    return $a==2?gethostbyaddr($ip):$ip; // kui a on 2, vljastame nime, muidu numbriliselt 
}
}
/**
* Sends argument to trace window.
* @param Mixed $data
* @param String $group
* @package DomBench
*/
function trace( $data, $group = 'trace' ) {
	if( DOM_MONITOR === true ) {
		DomMonitor::trace( $data, $group );
	}
}

/**
* Dom errorhandler
* @package DomErrorControl
*/
function DomErrorHandler( $errno, $errstr, $errfile, $errline ) {
	
	// dont allow throw if error started from loadClass or toString method. They don't allow throwing exceptions.
	/*$allowThrow = true;
	foreach( debug_backtrace() as $tr ) {
		if( $tr['function'] == 'loadClass' or $tr['function'] == '__toString' ) {
			$allowThrow = false;
			break;
		}
	}*/
	if (error_reporting() == 0)
        return;

    switch ($errno) {
		case E_USER_ERROR:
			$err = "<b>E_USER_ERROR</b> [$errno] $errstr<br />\n";
			$err.= "  on line $errline in file $errfile";
			$err.= ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
			$exception = new RuntimeException( $err, 0 );
			//if( $allowThrow === true ) {
			//	die(DomErrorControl::exceptionToString( $exception ));
			//} else {
				throw $exception;
			//}
		break;
	
		case E_USER_WARNING:
			$err = "<b>E_WARNING</b> [$errno] $errstr<br />\n";
			$err.= "  on line $errline in file $errfile";
			$err.= ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
			trace($err, 'PHP');
		break;
	
		case E_USER_NOTICE:
			trace("<b>E_USER_NOTICE</b> [$errno] $errstr<br />\n", 'PHP');
		break;
		
		case E_NOTICE:
			//trace("<b>E_NOTICE</b> [$errno] $errstr<br />\n", 'PHP');
		break;
		 
		case E_RECOVERABLE_ERROR:
			$err = "<b>E_RECOVERABLE_ERROR</b> [$errno] $errstr<br />\n";
			$err.= "  on line $errline in file $errfile";
			$err.= ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
			$exception = new RuntimeException( $err, 0 );
			//if( $allowThrow === true ) {
			//	die(DomErrorControl::exceptionToString( $exception ));
			//} else {
				throw $exception;
			//}
		break;
			
		case E_WARNING:
			$err = "<b>E_WARNING</b> [$errno] $errstr<br />\n";
			$err.= "  on line $errline in file $errfile";
			$err.= ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
			//trace($err, 'PHP');

			$exception = new RuntimeException( $err, 0 );
			throw $exception;
		break;
		
		default:
			$err = "<b>UNKNOWN ERROR</b> [$errno] $errstr<br />\n";
			$err.= "  on line $errline in file $errfile";
			$err.= ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
			trace($err, 'PHP');
		break;
    }
    return true;
}
/**
* Handles exception. (logs and returns nice error message)
* Uses DomDBLogger to log error to database.
* If DOM_SEND_ERRORS_TO_EMAIL is valid email address then all errors will be sent to this email.
* If error logging fails, it will try to send email to DOM_ADMIN_EMAIL about the problem. (Even if DOM_SEND_ERRORS_TO_EMAIL is valid email.)
* Sending emails depends on Mail class.
* @param Mixed $exception Takes in Exception or just String with error message.
* @return String
*/
function DomErrorReporter( $error ) {
	
	// If debugging mode is active then no logging or emailing is done. Full error is shown.
	if( $_SESSION['debugMode'] === true ) {
		if( $error instanceof Exception ) {
			return exceptionToString( $error, false );
		} else {
			return '<i>Detected error:</i> '.$error;
		}
	}
	
	if( $error instanceof Exception ) {
		$str = exceptionToString( $error, true );
		Dom::$lastError = exceptionToPlainString( $error );
	} else {
		$str = '<i>Detected error:</i> '.$error;
		Dom::$lastError = $error;
	}
	
	
	
	try {
		
		// log error.
		if( $error instanceof Exception ) {
			$messageForLog = $error->getMessage();
			$strForLog = exceptionToString( $error, false, true );
			$codeForLog = $error->getCode();
		} else {
			$messageForLog = $error;
			$strForLog = $error;
			$codeForLog = 0;
		}
	
		//debug( $exception );
		// try using database logger, if fails use file logger as second option.
		
		try {
			
			if( class_exists('DomDBLogger') == true )  {
				$log = new DomDBLogger('exceptions');
				$id = $log->log( $messageForLog, $strForLog, $codeForLog );
				if( $id != true ) {
					throw new Exception();
				}
			} else {
				throw new Exception();
			}
		} catch( Exception $e ) {
			if( class_exists('DomFileLogger') == true )  {
				$log = new DomFileLogger('exceptions');
				$id = $log->log( $messageForLog, $strForLog, $codeForLog );
				if( $id != true ) {
					throw new Exception();
				}
			}
		}
		
		// if email reporting is on.
		if( DOM_SEND_ERRORS_TO_EMAIL !== false && class_exists('Mail') == true ) {
			try {
				$mail = new Mail();
				$mail->FromName = SITE_URL;
				$mail->AddAddress(DOM_SEND_ERRORS_TO_EMAIL);
				$mail->IsHTML(true);
				$mail->Subject  =  "Tekkinud on viga (".$id.")!";
				$mail->Body     =  $strForLog;
				$mail->Send();
				
			} catch( RuntimeException $e ) {
			
			}
		}
		
	// fall back if logging is not working then try to send email to adminsitrator
	} catch( Exception $e ) {
		
		if( class_exists('Mail') == true) {
			try {
				$mail = new Mail();
				$mail->FromName = SITE_URL;
				$mail->AddAddress(DOM_ADMIN_EMAIL);
				$mail->IsHTML(true);
				$mail->Subject  =  "Tekkinud on viga ja errorite logimine ei tööta!";
				$mail->Body     =  $strForLog;
				$mail->Send();
			} catch( RuntimeException $e ) {
				
			}
		}
	}
	return $str;
}
/**
 * Handles not catched exceptions.
 *
 * @param Exception $e
 */
function DomDefaultExceptionHandler( Exception $e ) {
	echo DomErrorReporter( $e );
}
/**
* Shows nice error message for exceptions.
* If fails to load error template it will send email to DOM_ADMIN_EMAIL about the problem.
* @param Exception $exception
* @return String
*/
function exceptionToString( Exception $exception, $safe = true, $backtraceOpen = false ) {
	
	// finds out in which dir this file is located to relatively to document root		
	$TEMPLATE_URL = DOM_URL . 'DomErrorControl/Templates/'.DOM_DEFAULT_ERROR_STYLE.'/';
	$TEMPLATE_PATH = DOM_PATH . 'DomErrorControl/Templates/'.DOM_DEFAULT_ERROR_STYLE.'/';
	
	if( file_exists($TEMPLATE_PATH) === true ) {
		$REF = $_SERVER['HTTP_REFERER'];
		$URL = 'http://'.$_SERVER['HTTP_HOST'] . $_SERVER['REDIRECT_URL'];
		$SAFE = DOM_SAFE_ERRORS && $safe;
		$BACKTRACE_OPEN = (boolean)$backtraceOpen;
		
		ob_start();
		require( $TEMPLATE_PATH.'Error.php');
		$result = ob_get_contents();
		ob_end_clean();
	
		return $result;
	} else {
		
		$result = 'Tekkinud on viga! Palun liikuge tagasi <a href="javascript:history.go(-1)">siit</a>! ';
		
		try {
			$mail = new Mail();
			$mail->FromName = SITE_URL;
			$mail->AddAddress( DOM_ADMIN_EMAIL );
			$mail->IsHTML( true );
			$mail->Subject = "Tekkinud on viga!";
			$mail->Body  = 'Ei leita errori kuva faili: <strong>'.$TEMPLATE_PATH.'</strong> <br />';
			$mail->Body .= '<h3>Tekkinud vea exception oli: </h3>';
			$mail->Body .= $exception->__toString();
			$mail->Send();
		} catch( RuntimeException $e ) {
			$result .= 'Administraatoreid ei õnnestunud veast teavitada!';
		}
		
		return $result;
	}
}

function exceptionToPlainString( Exception $exception ) {
	$str = $exception->getCode().') '.$exception->getMessage()."\n";
	foreach( $exception->getTrace() as $desc) {
		$str .= 'called '.$desc['class'].$desc['type'].$desc['function'].' in '.dirname($desc['file']).'\\'.basename($desc['file']).' on line '.$desc['line']."\n";
	}
	return $str;
}
/**
* Constants that must be defined:
* DOM_CONF_DIR_PATH - path to configuration files dir
* DOM_CACHE_PATH - path where cache files can be stored
* DOM_URL - url to Dom.init.php dir. Its set automatically.
* DOM_PATH - dir where Dom.init.php is held. Its set automatically.
*
* This class silently implements DomMonitorable. Direct implementation is not possible, as class Dom also loads classes. This is a special case hard coded into DomMonitor.
* 
* @author Oliver Leisalu, Inditel Meedia OÜ
* @package Dom
*/
class Dom {

	private static $classPaths;
	
	private static $domarClassParserLockOwner = false;
	
	public static $lastError;

	public static function init() {
		self::$classPaths = DomCache::get('DOM_AUTOLOAD_PATHS');
		if( self::$classPaths == NULL ) {
			self::createClassesCache('Cache file did not exist!');
		}
	}
	
	public static function getClassPaths() {
		return self::$classPaths;
	}
	
	public static function getClassPath( $className ) {
		return self::$classPaths[$className];
	}
	
	
	public static function parseDomarClass( $className ) {
		
		$fileExists = file_exists(self::$classPaths[$className]);
		$updateDomarFile = $fileExists && DOMAR_PUBLISHED === false && (filemtime(self::$classPaths[$className]) < filemtime(self::$classPaths[$className.'_DomAr']));
		if( $fileExists === false || $updateDomarFile === true || DOM_CACHE === false ) {
			
			DOM_MONITOR && $t = DomMonitorEntry::c( __CLASS__, "PARSE DOMAR CLASS ".$className );
			
			$lockCreator = false;
			if( self::$domarClassParserLockOwner == false ) {
				$lockCreator = true;
				$locker = new DomLocker('DOMAR_CLASS_PARSER');
				$locker->setWaitTime( DOM_AUTOLOAD_LOCKER_WAIT_TIME );
				$locker->setResetTime( DOM_AUTOLOAD_LOCKER_EXIST_TIME );
				if( $locker->lock() === false ) {
					$locker->wait();
					self::$classPaths = DomCache::get('DOM_AUTOLOAD_PATHS');
					return;
				}
			}
			self::$domarClassParserLockOwner = true;
			
			
			
			if( file_exists(self::$classPaths[$className.'_DomAr']) === false ) {
				//self::createClassesCache( 'DomAr class file for class '.$className.' not found' );
				//if( file_exists(self::$classPaths[$className.'_DomAr']) === false ) {
				die(DomErrorReporter(new RuntimeException('DomAr class file for class '.$className.' not found')));
				//}
			}
			DomArClassParser::get( $className )->save();

			self::$classPaths[$className] = DomArClassParser::getClassFilePath( $className );
			DomCache::cache('DOM_AUTOLOAD_PATHS', self::$classPaths, DOM_AUTOLOAD_CACHE_LIFE );
			
			
			
			if( $lockCreator === true ) {
				self::$domarClassParserLockOwner = false;
				$locker->unlock();
			}
			
			DOM_MONITOR && $t->end();
		}
	}
	
	public static function loadClass( $className ) {
		
		DOM_MONITOR && $t = DomMonitorEntry::c( __CLASS__, $className );
			
		try {
			
			$file = self::$classPaths[$className.'_DomAr'] ? self::$classPaths[$className.'_DomAr'] : self::$classPaths[$className];
			if( $file == null || file_exists($file) === false ) {
				self::createClassesCache( 'Class file for class '.$className.' not found' );
				
				if( self::$classPaths[$className.'_DomAr'] ) {
					self::parseDomarClass( $className );
				}
				
				$file = self::$classPaths[$className];
				if( $file == null || file_exists($file) === false ) {
					die(DomErrorReporter(new RuntimeException('Class file for class '.$className.' not found')));
				}
			} else if( self::$classPaths[$className.'_DomAr'] ) {
				self::parseDomarClass( $className );
			}
			
			require_once( self::$classPaths[$className] );

		} catch( Exception $e ) {
			die(DomErrorReporter($e));
		}
		
		DOM_MONITOR && $t->end();
	}
	
	
	private static function searchClasses( $path ) {
		global $DOM_CONF;
		$files = glob( $path.'*',GLOB_MARK);
		if( !is_array($files) ) 
			return;
			
		foreach( $files as $filePath) {
			if( is_dir($filePath) === true )
				self::searchClasses( $filePath );
			else
				self::parseFile( $filePath );
		}
	}
	
	private static function parseFile( $filePath ) {
		global $DOM_CONF;
		
		foreach( $DOM_CONF['skip_paths'] as $skip ) {
			if( preg_match($skip, $filePath ) ) 
				return;
		}
		
		$ok = false;
		foreach( $DOM_CONF['classes_suffix'] as $suffix ) {
			if( preg_match($suffix, $filePath ) ) {
				$ok = true;
			}
		}
		if( $ok === false ) {
			return;
		}
		
		$fileNameExp = explode('.',basename($filePath));
		$source = file_get_contents($filePath);
		$tokens = token_get_all($source);
		$foundClass = false;
		foreach( $tokens as $token ) {
		
			if( $token[0] === T_CLASS or $token[0] === T_INTERFACE ) {
				$foundClass = true;
			}
			
			if( $foundClass === true && $token[0] === T_STRING ) {
				$foundClass = false;
				
				$className = $token[1];

				if( isset(self::$classPaths[$className]) === true ) {
					die('<strong>Dom ERROR:</strong> Found class <strong>'.$className.'</strong> from: <br />
							1) <strong>'.$filePath.'</strong> <br />
							2) <strong>'.self::$classPaths[$className].'</strong>. <br />
							Which one to use?<br />
							To correct this problem remove one of the files or add rule to skip one of these!');
				
				} else if( $fileNameExp[1] === 'domar' ) {
					self::$classPaths[$className.'_DomAr'] = $filePath;
				
				} else {
					self::$classPaths[$className] = $filePath;
				}
			}
		}
	}
		
	public static function createClassesCache( $reason = '') {
		
		DOM_MONITOR && $t = DomMonitorEntry::c( __CLASS__, "CREATE CLASSES CACHE" );
		
		$locker = new DomLocker('DOM_AUTOLOAD_PATHS');
		$locker->setWaitTime( DOM_AUTOLOAD_LOCKER_WAIT_TIME );
		$locker->setResetTime( DOM_AUTOLOAD_LOCKER_EXIST_TIME );
		if( $locker->lock() === false ) {
			$locker->wait();
			self::$classPaths = DomCache::get('DOM_AUTOLOAD_PATHS');
			return;
		}
		$log = new DomFileLogger('DOM_AUTOLOAD_PATHS');
		$log->log('Creating files cache','Reason: '.$reason.' Exception string: '.exceptionToString(new Exception(), false, true) );
		
		self::$classPaths = array();
		
		global $DOM_CONF;
		require( DOM_CONF_DIR_PATH . 'classes.conf.php' );
		
		foreach( $DOM_CONF['search_paths'] as $path ) {
			self::searchClasses( $path );
		}
		
		DomCache::cache('DOM_AUTOLOAD_PATHS', self::$classPaths, DOM_AUTOLOAD_CACHE_LIFE );
			
		$locker->unlock();
		
		DOM_MONITOR && $t->end();
	}

	
	public static function databaseConnected() {
		if( DOM_MONITOR === true ) {
			DomMonitor::databaseConnected();
		}
	}
	
	public static function gzipPage() {
		if ( headers_sent() ) {
			trace('Page gzipping: <b>FAILED</b> (Headers already sent!)','Dom');
			return;
		}
		if ( preg_match ("/((?:x-)?gzip|deflate)/i", $_SERVER['HTTP_ACCEPT_ENCODING'], $_encoding_type) ) {
			if (strtolower (substr ($_encoding_type[1], -4)) == 'gzip') {
				function _GZippedPage_Callback ($data) {	
					$packedData	= gzcompress($data, 5);
					return "\x1f\x8b\x08\x00\x00\x00\x00\x00" . substr($packedData, 0, strlen($packedData) - 4) . pack('V', crc32($data)) . pack('V', strlen($data));
				}
			} else {			
				function _GZippedPage_Callback ($data) {
					return gzdeflate ($data, 5);			
				}
			}
			header ("Content-Encoding: ".$_encoding_type[1]);
			trace('Page gzipping is <b>ON</b>!','Dom');
			ob_implicit_flush (0);
			ob_start ("_GZippedPage_Callback");
		} else {
			trace('Page gzipping: <b>FAILED</b>!','Dom');
		}
	}
}


header("Content-Type: text/html; charset=utf-8");
error_reporting(E_ALL ^ E_NOTICE);
set_error_handler('DomErrorHandler');
set_exception_handler('DomDefaultExceptionHandler');

if( $_GET['debugMode'] == 'off' ) {
	$_SESSION['debugMode'] = false;
} else if( $_GET['debugMode'] == DOM_DEBUG_PASSWORD ) {
	$_SESSION['debugMode'] = true;
}
$dirname = dirname(__FILE__);

define('DOM_MONITOR', DOM_MONITOR_LOG === true or $_SESSION['debugMode'] === true );

require( $dirname.'/DomMonitor/DomMonitor.class.php');
require( $dirname.'/DomMonitor/DomMonitorEntry.class.php');
if( DOM_MONITOR === true ) {
	DomMonitor::init();
}
require_once($dirname.'/DomBase/DomBase.functions.php');
require_once($dirname.'/DomLocker/DomLocker.class.php');
require_once($dirname.'/DomCache/DomCache.class.php');
require_once($dirname.'/DomLogger/DomFileLogger.class.php');

DomCache::init();
Dom::init();

spl_autoload_register( array('Dom', 'loadClass') );
trace('DomErrorControl has safe errors turned <strong>'.(DOM_SAFE_ERRORS ? '<span style="color:green">ON</span>' : '<span style="color:green">OFF</span>').'</strong>!', 'Dom');

DomMonitor::gap('Dom initialized', 'initialize');
